﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace SKQMSLib
{
    public class ExpressionResult
    {
        public string id;
        public double value;
        public List<OperationOnUnknown> unkOps;//list of operations bound to an unknown

        public ExpressionResult()
        {
            id = "1";
            value = 0;
            unkOps = new List<OperationOnUnknown>();
        }
    }

    public class Expression
    {
        public ExpressionResult result;
        public string ExpressionString
        {
            get;
            private set;
        }
        public bool HasUnknown
        {
            get;
            private set;
        }

        public Expression(string argString)
        {
            result = new ExpressionResult();
            
            //check for pi and e
            if (argString.Contains("pi")) argString = argString.Replace("pi", Math.PI.ToString(CultureInfo.InvariantCulture.NumberFormat));
            if (argString.Contains("e")) argString = argString.Replace("e", Math.E.ToString(CultureInfo.InvariantCulture.NumberFormat));

            //check for unknown
            for (int i = 0; i < argString.Length; i++) 
                if (CheckIf.IsLetter(argString[i]))
                   HasUnknown = true;
            
            ExpressionString = argString;
            ParenthesesManager();
        }

        private void ParenthesesManager()
        {
            string mainString = ExpressionString;
            int pDepth = 0, maxPDepth = -1, i;

            if ((mainString.Contains('(') == true) && (mainString.Contains(')') == true))
            {
                //find the maximum depth and check for exceptions
                for (i = 0; i < mainString.Length; i++)
                {
                    if (i > 0 &&
                        mainString[i - 1] == '(' && mainString[i] == ')') throw new ParenthesisException("Empty parentheses !");

                    if (mainString[i] == '(')
                    {
                        pDepth++;
                        if (maxPDepth < pDepth) maxPDepth = pDepth;
                    }
                    
                    if (mainString[i] == ')')
                        pDepth--;
                }
                if (pDepth != 0) throw new ParenthesisException("Missing parenthesis !");

                int openPIndex = 0, closePIndex = 0;
                bool doEval = false, reachedMaxDepth = false;
                List<string> outerExprs = new List<string>();
                List<string> innerExprs = new List<string>();
                
                //evaluate every expression at every depth
                while (maxPDepth >= 0)
                {
                    for (i = 0; i < mainString.Length; i++) 
                    {
                        if (mainString[i] == '(')
                        {
                            pDepth++;
                            if (pDepth == maxPDepth)
                            { 
                                openPIndex = i;
                                reachedMaxDepth = true;
                            }
                        }
                        if (mainString[i] == ')')
                        {
                            pDepth--;
                            if (reachedMaxDepth == true)
                            {
                                closePIndex = i;
                                reachedMaxDepth = false;
                                doEval = true;
                            }
                        }
                        if (doEval == true)
                        {
                            outerExprs.Add(mainString.Substring(openPIndex, closePIndex - openPIndex + 1));
                            innerExprs.Add(mainString.Substring(openPIndex + 1, closePIndex - openPIndex - 1));
                            doEval = false;
                        }
                    }

                    for (i = 0; i < outerExprs.Count; i++) 
                        mainString = mainString.Replace(outerExprs[i], Evaluate(innerExprs[i]));

                    outerExprs.Clear();
                    innerExprs.Clear();
                    maxPDepth--;
                }
            }
            mainString = Evaluate(mainString);
            if (HasUnknown == false) result.value = Convert.ToDouble(mainString);
        }

        private string Evaluate(string mainString) 
        {
            //check for invalid characters
            int i;
            for (i = 0; i < mainString.Length; i++)
                if (!CheckIf.IsNumber(mainString[i]) && !CheckIf.IsLetter(mainString[i]))
                    switch (mainString[i])
                    {
                        case '.': break;  case '^': break;  case '/': break;  case '*': break;
                        case '+': break;  case '-': break;  case '~': break;  case ' ': break; 
                        default: throw new UnkownCharacterException("Unknown character found !");
                    }

            //find unknown
            string unkownString = "";
            bool foundUnkown = false;
            for (i = 0; i < mainString.Length; i++)
            {
                if (CheckIf.IsLetter(mainString[i]))
                {
                    if (foundUnkown == true) throw new UnknownRelatedException("Only one unknown is allowed !");
                    else unkownString += mainString[i];
                }

                if (foundUnkown == false && i > 0 && CheckIf.IsLetter(mainString[i - 1]))
                    foundUnkown = true;
            }
            result.id = unkownString;

            //cycle through potential operations
            int opStringIndex;
            Operations ops = new Operations();
            do
            {
                //replace "+-" and "-+" with "-"
                if (mainString.Contains("+-")) mainString = mainString.Replace("+-", "-");
                if (mainString.Contains("-+")) mainString = mainString.Replace("-+", "-");

                if (ops.Get() == '/') if (mainString.Contains("/0")) throw new DivideByZeroException("Can not divide by zero !");

                if (mainString.Contains(ops.Get()))
                {
                    opStringIndex = mainString.IndexOf(ops.Get());

                    if (opStringIndex == mainString.Length - 1) throw new InvalidSignPositionException("Operators at the end of equation are not allowed !");
                    
                    if (opStringIndex == 0)
                    {
                        //remove the plus sign from the beginning of mainString
                        switch (ops.Get())
                        {
                            case '+':
                                mainString = mainString.Remove(0, 1);
                                continue;
                            case '-':
                                if (mainString.IndexOf('-') == mainString.LastIndexOf('-'))
                                {
                                    return mainString;
                                }
                                break;
                            default:
                                throw new InvalidSignPositionException("Invalid sign at the beginning of the equation !");
                        }
                    }
                    
                    //get left operand
                    i = opStringIndex - 1;
                    string leftOperandString = "";
                    bool bindMinusToUnkown = false, unknownIsOnLeft = false;

                    if (CheckIf.IsLetter(mainString[i]) || CheckIf.IsNumber(mainString[i]))
                    {
                        if (CheckIf.IsLetter(mainString[i])) unknownIsOnLeft = true;
                        while (true)
                        {
                            //'~' binds minus to the base e.g.: ~2^2 is interpreted as (-2)^2, not -(2^2)
                            if (mainString[i] == '~')
                            {
                                if (unknownIsOnLeft == true) bindMinusToUnkown = true;
                                else leftOperandString += "-";
                                //~ also marks the beginning of our expression
                                break;
                            }

                            leftOperandString += mainString[i];
                            //check for the beginning of mainString
                            if (i == 0) break;
                            else
                            {
                                //check the next character
                                if (CheckIf.IsLetter(mainString[i - 1]) || CheckIf.IsNumber(mainString[i - 1]) ||
                                    (mainString[i - 1] == '.') || (mainString[i - 1] == '~'))
                                    i--;
                                else break;
                            }
                        }
                    }
                    else throw new InvalidSignPositionException("Invalid sign position !");
                    int beginning = i;
                   

                    //get right operand
                    i = opStringIndex + 1;
                    string rightOperandString = "";
                    bool unknownIsOnRight = false;
                    bindMinusToUnkown = false;

                    //skip unnecessary plus sign
                    if (mainString[i] == '+') i++;
                    
                    if (mainString[i] == '-')
                    {
                        rightOperandString += '-';
                        bindMinusToUnkown = true;
                        i++;
                    }
                    
                    if (CheckIf.IsLetter(mainString[i]) || CheckIf.IsNumber(mainString[i]))
                    {
                        if (CheckIf.IsLetter(mainString[i])) unknownIsOnRight = true;
                        while (true)
                        {
                            rightOperandString += mainString[i];
                            //check for the end of mainString
                            if (i == mainString.Length - 1) break;
                            else
                            {
                                //check the next character
                                if (CheckIf.IsLetter(mainString[i + 1]) || CheckIf.IsNumber(mainString[i + 1]) ||
                                    (mainString[i + 1] == '.'))
                                    i++;
                                else break;
                            }
                        }
                    }
                    else throw new InvalidSignPositionException("Invalid sign position !");
                    int end = i;


                    //reverse leftOperandString
                    string tempString = "";
                    for (i = leftOperandString.Length - 1; i >= 0; i--) tempString += leftOperandString[i];
                    leftOperandString = tempString;

                    //start the calculation
                    bool appendPlusSign = false;
                    double leftOperandDouble = 0, rightOperandDouble = 0;
                    //get the current expression from mainString
                    string oldString = mainString.Substring(beginning, end - beginning + 1);

                    if (unknownIsOnLeft == true || unknownIsOnRight == true)
                    {
                        if (unknownIsOnLeft)
                        {
                            rightOperandDouble = Convert.ToDouble(rightOperandString);
                            if (CheckIf.IsOutOfBounds(rightOperandDouble)) throw new NumberOutOfBoundsException("Value is out of bounds !");
                        }
                        if (unknownIsOnRight)
                        {
                            leftOperandDouble = Convert.ToDouble(leftOperandString);
                            if (CheckIf.IsOutOfBounds(leftOperandDouble)) throw new NumberOutOfBoundsException("Value is out of bounds !");
                        }
                        
                        if (bindMinusToUnkown) result.unkOps.Add(new OperationOnUnknown('*', -1));
                        //add operation to the unknown's list and remove it from mainString
                        switch (ops.Get())
                        {
                            case '^':
                                throw new UnknownRelatedException("This operation is not supported !");

                            case '/':
                                if (unknownIsOnLeft)
                                    result.unkOps.Add(new OperationOnUnknown('*', 1 / rightOperandDouble));
                                
                                if (unknownIsOnRight)
                                {
                                    result.unkOps.Add(new OperationOnUnknown('/', leftOperandDouble));
                                    appendPlusSign = true;
                                }
                                break;

                            case '*':
                                if (unknownIsOnLeft)
                                    result.unkOps.Add(new OperationOnUnknown('*', rightOperandDouble));
                                
                                if (unknownIsOnRight)
                                {
                                    result.unkOps.Add(new OperationOnUnknown('*', leftOperandDouble));
                                    appendPlusSign = true;
                                }
                                break;

                            case '+':
                                if (unknownIsOnLeft)
                                    result.unkOps.Add(new OperationOnUnknown('+', rightOperandDouble));

                                if (unknownIsOnRight)
                                    result.unkOps.Add(new OperationOnUnknown('+', leftOperandDouble));
                                break;

                            case '-':
                                if (unknownIsOnLeft)
                                    result.unkOps.Add(new OperationOnUnknown('-', rightOperandDouble));

                                if (unknownIsOnRight)
                                {
                                    result.unkOps.Add(new OperationOnUnknown('*', -1));
                                    result.unkOps.Add(new OperationOnUnknown('+', leftOperandDouble));
                                }
                                break;
                        }

                        if (appendPlusSign) mainString = mainString.Replace(oldString, ("+" + result.id));
                        else mainString = mainString.Replace(oldString, result.id);
                    }
                    else //if there is no unknown, calculate the operationResult
                    {
                        double operationResult = 0;
                        string resultString = "";

                        leftOperandDouble = Convert.ToDouble(leftOperandString);
                        if (CheckIf.IsOutOfBounds(leftOperandDouble)) throw new NumberOutOfBoundsException("Value is out of bounds !");

                        rightOperandDouble = Convert.ToDouble(rightOperandString);
                        if (CheckIf.IsOutOfBounds(rightOperandDouble)) throw new NumberOutOfBoundsException("Value is out of bounds !");

                        switch (ops.Get())
                        {
                            case '^':
                                operationResult = Math.Pow(leftOperandDouble, rightOperandDouble);
                                break;

                            case '/':
                                operationResult = leftOperandDouble / rightOperandDouble;
                                break;

                            case '*':
                                operationResult = leftOperandDouble * rightOperandDouble;
                                break;

                            case '+':
                                operationResult = leftOperandDouble + rightOperandDouble;
                                break;

                            case '-':
                                operationResult = leftOperandDouble - rightOperandDouble;
                                break;
                        }
                        if (CheckIf.IsOutOfBounds(operationResult)) throw new NumberOutOfBoundsException("Value is out of bounds !");
                        
                        resultString = operationResult.ToString(CultureInfo.InvariantCulture.NumberFormat);

                        if (operationResult >= 0) mainString = mainString.Replace(oldString, '+' + resultString);
                        else mainString = mainString.Replace(oldString, resultString);
                    }
                }
            } while (ops.Next());
            return mainString;
        }
    }
}